FFT & NTT

0. 符号约定

我们用大写字母表示多项式,小写字母表示多项式的系数,比如:

A(x)=a0+a1x+a2x2++anxn

ωnk 表示单位根,即

ωnk=cos2kπn+isin2kπn

1. FFT

0. 基本思路

考虑对两个多项式进行乘法:

C(x)=A(x)B(x)

显然系数满足

cn=k=0nakbnk
卷积

形如

cn=jk=najbk

的式子叫作卷积。
对于多项式乘法,所计算的就是加法卷积。

朴素计算显然是 Θ(n2) 的,很不优秀。这种不优秀实质上来源于系数表示,我们需要用其他的方法表示多项式。

注意到对于 (n1) 次多项式,我们可以用 n 个不同的点处的值来进行唯一表示。因此我们可以对多项式 A 求出其点值 A(x0),A(x1),,A(xn1),对于多项式 B 同理,求出 B(x0),B(x1),,B(xn1) 后,乘积 C(x) 对应的点值显然有

C(xk)=A(xk)B(xk)

然后再进行插值即可得到 C 的系数表示。

Caution

上面的 nC 的次数 +1

如此我们将 Θ(n2) 的朴素卷积转化为了 Θ(n) 的点值的乘法,关键在于求点值和插值两步的优化。

1. 求点值(DFT)

如果随意代入 xk,复杂度仍为 Θ(n2),没有优化。但是 xi 是可以任意选取的,我们可以选取更容易计算的点进行计算。这里的技巧是代入单位根 xk=ωnk

A(x)=a0+a1x++an1xn1(次数不够时高次项系数视为 0 即可),有

A(xk)=j=0n1ajωnjk

利用单位根 ωndkd=ωnk 的性质,我们能够轻易处理数列的拉伸操作,进而通过分治解决问题。

这里以二分治为例。方便起见将 n 补为 2 的幂。

奇偶分组便于使用拉伸操作:

A0(x)=a0+a2x++an2xn/21A1(x)=a1+a3x++an1xn/21,则有

A(x)=A0(x2)+xA1(x2)

代入单位根立得

A(ωnk)=A0(ωn/2k)+ωnkA1(ωn/2k)

容易发现 A0(ωn/2k)A1(ωn/2k) 即为两个子问题,因此可以分治求解……吗?

这里有一个问题,子问题中 k 的取值仅为 0,1,,n/21,还有另一半需要计算。

因此我们还需要代入 ωnk+n/2=ωnk,进行计算得

A(ωnk+n/2)=A0(ωn/2k)ωnkA1(ωn/2k)

至此我们已经完成了快速计算点值的操作。由 Master 定理易知时间复杂度 Θ(nlogn)

实质上 DFT 是一个序列变换的过程,它将 a0,,an1 变换为了 A(x0),,A(xn1)
并且这是一个线性的过程:

(11111ωn1ωn2ωnn11ωn2ωn4ωn2(n1)1ωnn1ωn2(n1)ωn(n1)(n1))(a0a1a2an1)=(A(x0)A(x1)A(x2)A(xn1))

设 DFT 将序列 X 变换为序列 Y,记作 DFT(X)=Y,根据线性性得到:

2. 插值(IDFT)

利用上面的观点,容易发现 IDFT 只需把上面矩阵的逆矩阵求出即可。而上面的矩阵是 Vandermonde 矩阵,逆矩阵熟知。

如果不会线代也可以直接启动拉格朗日插值:

A(x)=k=0n1A(xk)jkxxjxkxj

注意,利用单位根的性质,后面那个乘积项实际上是可以直接算出的,分子:

jk(xxj)=xn1xωnk

分母:

jk(xkxj)=limxxkxn1xωnk=nωnk(n1)=nωnk

于是

jkxxjxkxj=ωnknxn1xωnk=1n(11ωnkxxn1ωnkx)=1n(j=0+ωnjkxjj=0+ωnjkxj+n)=1n(j=0+ωnjkxjj=0+ωn(j+n)kxj+n)=1nj=0n1ωnjkxj

代入整理得

A(x)=k=0n1A(xk)1nj=0n1ωnjkxj=j=0n1(1nk=0n1A(xk)ωnjk)xj

于是(这里更换了下标以保证形式与 DFT 的统一)

ak=1nj=0n1A(xj)ωnjk

对比 DFT 的式子

A(xk)=j=0n1ajωnjk

发现 IDFT 只不过是将单位根换成了 ωnj 并多了一个 1n 的系数。
在性质上我们实际上无法区分 ωnjωnj,算法与先前 DFT 完全一致。

上面关于 IDFT 的证明虽然直接但略显复杂,一个简单一些的证明可以看容斥与反演概论的最后一章。

3. 代码实现

To be finished